import { Construct } from 'constructs';
import { CfnInstanceProfile } from './iam.generated';
import { ServicePrincipal } from './principals';
import { IRole, Role } from './role';
import { Resource, Arn, Stack, IResource, PhysicalName } from '../../core';

/**
 * Represents an IAM Instance Profile
 */
export interface IInstanceProfile extends IResource {
  /**
   * The InstanceProfile's name.
   * @attribute
   */
  readonly instanceProfileName: string;

  /**
   * The InstanceProfile's ARN.
   * @attribute
   */
  readonly instanceProfileArn: string;

  /**
   * The role associated with the InstanceProfile.
   */
  readonly role?: IRole;
}

/**
 * Properties of an Instance Profile
 */
export interface InstanceProfileProps {
  /**
   * An IAM role to associate with the instance profile that is used by EC2 instances.
   *
   * The role must be assumable by the service principal `ec2.amazonaws.com`:
   *
   * @example
   * const role = new iam.Role(this, 'MyRole', {
   *   assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
   * });
   *
   * @default - a role will be automatically created, it can be accessed via the `role` property
   */
  readonly role?: IRole;

  /**
   * The name of the InstanceProfile to create.
   *
   * @default - generated by CloudFormation
   */
  readonly instanceProfileName?: string;

  /**
   * The path to the InstanceProfile.
   *
   * @default /
   */
  readonly path?: string;
}

/**
 * Attributes of an Instance Profile
 */
export interface InstanceProfileAttributes {
  /**
   * The ARN of the InstanceProfile.
   *
   * Format: arn:<partition>:iam::<account-id>:instance-profile/<instance-profile-name-with-path>
   */
  readonly instanceProfileArn: string;

  /**
   * The role associated with the InstanceProfile.
   *
   * @default - no role
   */
  readonly role?: IRole;
}

/**
 * Base class for an Instance Profile
 */
abstract class InstanceProfileBase extends Resource implements IInstanceProfile {
  public abstract readonly instanceProfileName: string;
  public abstract readonly instanceProfileArn: string;

  /**
   * The role associated with the InstanceProfile.
   * @internal
   */
  protected _role?: IRole;

  /**
   * Returns the role associated with this InstanceProfile.
   */
  public get role(): IRole | undefined {
    return this._role;
  }
}

/**
 * IAM Instance Profile
 */
export class InstanceProfile extends InstanceProfileBase {
  /**
   * Import an existing InstanceProfile from an InstanceProfile name.
   *
   * @param scope construct scope
   * @param id construct id
   * @param instanceProfileName the name of the existing InstanceProfile to import
   */
  public static fromInstanceProfileName(scope: Construct, id: string, instanceProfileName: string): IInstanceProfile {
    const instanceProfileArn = Stack.of(scope).formatArn({
      service: 'iam',
      region: '',
      resource: 'instance-profile',
      resourceName: instanceProfileName,
    });
    return InstanceProfile.fromInstanceProfileAttributes(scope, id, { instanceProfileArn });
  }

  /**
   * Import an existing InstanceProfile from an InstanceProfile ARN.
   *
   * If the ARN comes from a Token, the InstanceProfile cannot have a path; if so, any attempt
   * to reference its instanceProfileName will fail.
   *
   * @param scope construct scope
   * @param id construct id
   * @param instanceProfileArn the ARN of the exiting InstanceProfile to import
   */
  public static fromInstanceProfileArn(scope: Construct, id: string, instanceProfileArn: string): IInstanceProfile {
    return InstanceProfile.fromInstanceProfileAttributes(scope, id, { instanceProfileArn });
  }

  /**
   * Import an existing InstanceProfile from given InstanceProfile attributes.
   *
   * If the ARN comes from a Token, the InstanceProfile cannot have a path; if so, any attempt
   * to reference its instanceProfileName will fail.
   *
   * @param scope construct scope
   * @param id construct id
   * @param attrs the attributes of the InstanceProfile to import
   */
  public static fromInstanceProfileAttributes(scope: Construct, id: string, attrs: InstanceProfileAttributes): IInstanceProfile {
    class Import extends InstanceProfileBase {
      public readonly instanceProfileName: string = Arn.extractResourceName(attrs.instanceProfileArn, 'instance-profile').split('/').pop()!;
      public readonly instanceProfileArn: string = attrs.instanceProfileArn;

      constructor(s: Construct, i: string) {
        super(s, i);
        this._role = attrs.role;
      }
    }
    return new Import(scope, id);
  }

  /**
   * Returns the name of this InstanceProfile.
   */
  public readonly instanceProfileName: string;

  /**
   * Returns the ARN of this InstanceProfile.
   */
  public readonly instanceProfileArn: string;

  constructor(scope: Construct, id: string, props: InstanceProfileProps = {}) {
    super(scope, id, { physicalName: props.instanceProfileName });

    this._role = props.role || new Role(this, 'InstanceRole', {
      roleName: PhysicalName.GENERATE_IF_NEEDED,
      assumedBy: new ServicePrincipal('ec2.amazonaws.com'),
    });

    const instanceProfile = new CfnInstanceProfile(this, 'Resource', {
      roles: [this._role.roleName],
      instanceProfileName: this.physicalName,
      path: props.path,
    });

    this.instanceProfileName = this.getResourceNameAttribute(instanceProfile.ref);
    this.instanceProfileArn = this.getResourceArnAttribute(instanceProfile.attrArn, {
      region: '',
      service: 'iam',
      resource: 'instance-profile',
      resourceName: `${props.path ? props.path.substring(props.path.charAt(0) === '/' ? 1 : 0) : ''}${this.physicalName}`,
    });
  }
}
